import { CCCAgentsError } from '../CCCAgentsError';
import { InplaceArray } from '../InplaceArray';
import { DimensionProperty, ObservationType } from './ISensor';

/**
 * @en
 * A description of the observations that an ISensor produces.
 * This includes the size of the observation, the properties of each dimension, and how the observation
 * should be used for training.
 * 
 * @zh
 * ISensor 生成的观测的描述。
 * 这包括观测的大小，每个维度的属性以及观测如何用于训练。
 * 
 */
export class ObservationSpec {
	/**
	 *@en
	 * The size of the observations that will be generated.
     * For example, a sensor that observes the velocity of a rigid body (in 3D) would use [3].
     * A sensor that returns an RGB image would use [Height, Width, 3].
	 * 
	 * @zh
	 * 将生成的观测的大小。
	 * 例如，观察刚体速度（3D）的传感器将使用 [3]。
	 * 返回 RGB 图像的传感器将使用 [Height, Width, 3]。
	 * 
	 */
	private _shape: InplaceArray<number> = null!;
	public get shape(): InplaceArray<number> {
		return this._shape;
	}
	public set shape(value: InplaceArray<number>) {
		this._shape = value;
	}

	/**
	 * @en
	 * The properties of each dimensions of the observation.
     * The length of the array must be equal to the rank of the observation tensor.
	 * 
	 * @zh
	 * 观测的每个维度的属性。
	 * 数组的长度必须等于观测张量的秩。
	 */
	private _dimensionProperties: InplaceArray<DimensionProperty> = null!;
	public get dimensionProperties(): InplaceArray<DimensionProperty> {
		return this._dimensionProperties;
	}
	public set dimensionProperties(value: InplaceArray<DimensionProperty>) {
		this._dimensionProperties = value;
	}

	/**
	 * @en
	 * The type of the observation, e.g. whether they are generic or
     * help determine the goal for the Agent.
	 * 
	 * @zh
	 * 观测的类型，例如它们是通用的还是帮助确定代理的目标。
	 * 
	 */
	private _observationType: ObservationType = ObservationType.Default;
	public get observationType(): ObservationType {
		return this._observationType;
	}
	public set observationType(value: ObservationType) {
		this._observationType = value;
	}

	/**
	 * @en
	 *  The number of dimensions of the observation.
	 * 
	 * @zh
	 * 观测的维度数。
	 */
	get rank(): number {
		return this.shape.length;
	}

	/**
	 * @en
	 * Construct an ObservationSpec for 1-D observations of the requested length.
	 * @zh
	 * 为请求的长度构造一个 1-D 观测的 ObservationSpec。
	 * 
	 * @param length 
	 * @param obsType 
	 * @returns 
	 */
	static vector(length: number, obsType: ObservationType = ObservationType.Default): ObservationSpec {
		return new ObservationSpec(
			new InplaceArray<number>(length),
			new InplaceArray<DimensionProperty>(DimensionProperty.None),
			obsType
		);
	}

	/**
	 * @en
	 * Construct an ObservationSpec for variable-length observations.
	 * @zh
	 * 为可变长度观测构造一个 ObservationSpec。
	 * 
	 * @param obsSize 
	 * @param maxNumObs 
	 * @returns 
	 */
	static variableLength(obsSize: number, maxNumObs: number): ObservationSpec {
		const dimProps = new InplaceArray<DimensionProperty>(DimensionProperty.VariableSize, DimensionProperty.None);
		return new ObservationSpec(new InplaceArray<number>(obsSize, maxNumObs), dimProps);
	}

	/**
	 * @en
	 * Construct an ObservationSpec for visual-like observations, e.g. observations
     * with a height, width, and possible multiple channels.
	 * 
	 * @zh
	 * 为类似视觉的观测构造一个 ObservationSpec，例如具有高度、宽度和可能多个通道的观测。
	 * 
	 * @param height 
	 * @param width 
	 * @param channels 
	 * @param obsType 
	 * @returns 
	 */
	static visual(height: number, width: number, channels: number, obsType: ObservationType = ObservationType.Default) {
		const dimProps = new InplaceArray<DimensionProperty>(
			DimensionProperty.TranslationalEquiveriance,
			DimensionProperty.TranslationalEquiveriance,
			DimensionProperty.None
		);

		return new ObservationSpec(new InplaceArray<number>(height, width, channels), dimProps, obsType);
	}

	/**
	 * @en
	 * Create a general ObservationSpec from the shape, dimension properties, and observation type.
	 * @zh
	 * 从形状、维度属性和观测类型创建一个通用的 ObservationSpec。
	 * 
	 * @param shape 
	 * @param dimensionProperties 
	 * @param observationType 
	 */
	constructor(
		shape: InplaceArray<number>,
		dimensionProperties: InplaceArray<DimensionProperty>,
		observationType: ObservationType = ObservationType.Default
	) {
		if (shape.length !== dimensionProperties.length) {
			throw new CCCAgentsError(`shape and dimensionProperties must have the same length`);
		}

		this._shape = shape;
		this._dimensionProperties = dimensionProperties;
		this._observationType = observationType;
	}
}
